﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Animations;
using System.Diagnostics;

namespace Silvermoon.UI
{
    public class PropertyAccessor<C, T> : PropertyAccessor where C : IPropertyObject
    {
        #region ctors

        public PropertyAccessor(string property, ValueGetterDelegate<C, T> getter, ValueSetterDelegate<C, T> setter)
        {
            this.Name = property;
            GetValue = getter;
            SetValue = setter;
        }

        #endregion
        #region props

        public string Name { get; private set; }

        /// <summary>
        /// Gets the property value from an object.
        /// </summary>
        public readonly ValueGetterDelegate<C, T> GetValue;

        /// <summary>
        /// Sets the property value of an object.
        /// </summary>
        public readonly ValueSetterDelegate<C, T> SetValue;

        #endregion
        #region methods

        private PropertyTracking<T> GetOrCreatePropertyTracking(C obj)
        {
            PropertyTracking tracking = obj.GetPropertyTracking(this);
            if (tracking != null) return (PropertyTracking<T>)tracking;

            PropertyTracking<T> result = new PropertyTracking<T>();
            result.OriginalValue = GetValue(obj);
            obj.SetPropertyTracking(this, result);
            return result;
        }


        private Animation<T> GetAnimation(C obj)
        {
            PropertyTracking<T> value = GetOrCreatePropertyTracking(obj);
            Animation<T> animation = value.CurrentAnimation;
            if (animation == null)
            {
                animation = Animation<T>.CreateAnimation();
                value.CurrentAnimation = animation;
            }
            else
            {
                animation.ClearDelegates();
            }
            animation.Progress += (s, e) => { if (!obj.IsDisposing) this.SetValue(obj, e.Value); };
            return animation;
        }

        /// <summary>
        /// Gets whether the property of an object is in an animated state.
        /// </summary>
        /// <param name="obj">Object that contains the property.</param>
        /// <returns>True if the property is in an animated state, otherwise false.</returns>
        public bool IsAnimated(IPropertyObject obj)
        {
            return (obj.GetPropertyTracking(this) != null);
        }

        public void SetOriginalValue(C obj, T value)
        {
            PropertyTracking<T> propertyValue = GetOrCreatePropertyTracking(obj);
            propertyValue.OriginalValue = value;
        }

        public override void Prepare(Animation a, IPropertyObject o)
        {
            C obj = (C)o;
            Transition<T> transition = a.Transition as Transition<T>;
            object fromO = transition.GetFrom();
            T from = fromO != null ? (T)fromO : GetValue(obj);

            object toO = transition.GetTo();
            T to = toO != null ? (T)toO : GetUnanimatedValue(obj);

            Animation<T> currentAnimation = a as Animation<T>;
            currentAnimation.From = from;
            currentAnimation.To = to;
        }

        /// <summary>
        /// Begins to animate the property.
        /// </summary>
        /// <param name="obj">Object that contains the property.</param>
        /// <param name="transition">Transition..</param>
        public void BeginAnimation(C obj, Transition<T> transition, int duration)
        {
            transition.Property = this;
            duration = Math.Max(0, duration);
            Animation<T> currentAnim = GetAnimation(obj);
            StopAnimation(obj);
            currentAnim.Transition = transition;
            currentAnim.PropertyAccessor = this;
            currentAnim.StartDelay = transition.Delay;
            currentAnim.Duration = duration;
            if (transition.EasingFunction != null) currentAnim.EasingFunction = transition.EasingFunction;
            if (transition.Tracking == TrackMode.Keep)
            {
                // remove animation state after completition:
                currentAnim.Completed += (s, e) => obj.SetPropertyTracking(this, null);
            }
            if (currentAnim.HasCompletedEvent)
            {
                currentAnim.Completed += (s, e) => transition.OnCompleted();
            }

            // do not start the animation immidiately, but after control is preloaded and initialized:
            if (!obj.IsInitialized)
            {
                obj.EnqueueAnimation(currentAnim);
            }
            else
            {
                Prepare(currentAnim, obj);
                currentAnim.InvokeAsync();
            }
        }

        public void BeginAnimation(C obj, Transition<T> transition)
        {
            BeginAnimation(obj, transition, transition.Duration);
        }

        /// <summary>
        /// Animates and keeps the value.
        /// Not that this method override Transition.Tacking to TrackMode.Keep.
        /// </summary>
        /// <param name="obj">object to animate.</param>
        /// <param name="transition">Transition to perform.</param>
        public void Animate(C obj, Transition<T> transition)
        {
            transition.Tracking = TrackMode.Keep;
            BeginAnimation(obj, transition, transition.Duration);
        }


        /// <summary>
        /// Ends animation state and animates the property to it's original value if not already set.
        /// </summary>
        /// <param name="obj">Object that contains the property.</param>
        /// <param name="duration">Duration for animation.</param>
        internal void EndAnimation(C obj, int duration)
        {
            if (obj.IsDisposing) return;
            duration = Math.Max(0, duration);
            T value = GetValue(obj);
            T orig = GetUnanimatedValue(obj);
            StopAnimation(obj);
            if (value.Equals(orig)) duration = 0;


            if (duration > 0)
            {
                Animation<T> currentAnim = GetAnimation(obj);
                currentAnim.Abort();
                currentAnim.StartDelay = 0;
                currentAnim.Duration = duration;
                currentAnim.To = orig;
                currentAnim.From = value;
                currentAnim.Completed += (s, e) =>
                {
                    if (e.IsCompleted)
                    {
                        SetValue(obj, orig);
                        obj.SetPropertyTracking(this, null);
                    }
                };
                currentAnim.InvokeAsync();
            }
            else
            {
                SetValue(obj, orig);
                obj.SetPropertyTracking(this, null);
            }
        }


        /// <summary>
        /// Stops property animation if running and leaves the value.
        /// </summary>
        /// <param name="obj">Object that contains the property.</param>
        public void StopAnimation(C obj)
        {
            PropertyTracking<T> tracking = obj.GetPropertyTracking(this) as PropertyTracking<T>;
            if (tracking != null)
            {
                Animation<T> animation = tracking.CurrentAnimation;
                if (animation != null)
                {
                    animation.Abort();
                }
            }
        }


        public override void StopAnimation(IPropertyObject obj)
        {
            StopAnimation((C)obj);
        }

        /// <summary>
        /// Gets the original value of the property before any animation started.
        /// </summary>
        /// <param name="obj">Object that contains the property.</param>
        /// <returns>Original value of the property.</returns>
        public T GetUnanimatedValue(C obj)
        {
            PropertyTracking<T> tracking = obj.GetPropertyTracking(this) as PropertyTracking<T>;

            if (tracking == null) return GetValue(obj);
            return tracking.OriginalValue;
        }

        /// <summary>
        /// Set the original value of the property when no animation is in progress.
        /// </summary>
        /// <param name="obj">Object that contains the property.</param>
        /// <param name="value">Value for the property when no animation is in progress.</param>
        public void SetUnanimatedValue(C obj, T value)
        {
            if (!IsAnimated(obj))
            {
                SetValue(obj, value);
            }
            else
            {
                PropertyTracking<T> tracking = GetOrCreatePropertyTracking(obj);
                tracking.OriginalValue = value;
            }
        }

        public override void BeginAnimation(IPropertyObject obj, Transition transition)
        {
            BeginAnimation((C)obj, (Transition<T>)transition, transition.Duration);
        }

        internal override void BeginAnimation(IPropertyObject obj, Transition transition, int duration)
        {
            BeginAnimation((C)obj, (Transition<T>)transition, duration);
        }

        public override void EndAnimation(IPropertyObject obj, int duration)
        {
            EndAnimation((C)obj, duration);
        }

        public T GetTargetValue(C obj)
        {
            PropertyTracking<T> tracking = obj.GetPropertyTracking(this) as PropertyTracking<T>;
            if (tracking != null && tracking.CurrentAnimation != null && tracking.CurrentAnimation.IsRunning) return tracking.CurrentAnimation.To;
            return GetValue(obj);
        }

        public override string ToString()
        {
            return Name;
        }


        #endregion
    }
}
